/**
    plugin.c - Loads plugins using dlopen, dlsym, and other such functions.
    Copyright (C) 2009  Abram Magner

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>

#include "list.h"
#include "woofs_plugin.h"
#include "plugin.h"

//#define PLUGIN_TEST

static format_plugin *new_format_plugin (void *);
static void free_format_plugin (format_plugin *);

static void *plugin_load (char *);
static int plugin_close (void *);
static void *plugin_getsym (void *, char *);



//=============================================================================
// Find a plugin in the given set that can handle the given file, opened with
// file descriptor fd.
//=============================================================================
format_plugin *plugin_claimfile (plugin_set *pset, int fd, char *filename)
{
  plugin_set_iter *it = NULL;

  it = plugin_set_get_iter (pset);
  if (it == NULL) return (NULL);

  while (plugin_set_iter_hasmore (it))
    {
      format_plugin *pl = it->data;
      int res = pl->plugin_struct->claim_file (fd, filename);
      lseek (fd, 0, SEEK_SET);
      if (res) return (pl);
      plugin_set_iter_advance (&it);
    }
  return (NULL);
}

//=============================================================================
// Add all plugins in the given directory and its subdirectories to the given 
// plugin set.
//=============================================================================
int plugin_scandir (plugin_set *pset, char *directory)
{
  DIR *dir = NULL;
  struct dirent *entry = NULL;

  dir = opendir (directory);
  if (dir == NULL) return (-1);
  while ((entry = readdir (dir)) != NULL)
    {
      char *name = NULL;
      size_t namesize = strlen (directory) + strlen (entry->d_name) + 2;
      struct stat statbuf;
      if (strcmp (entry->d_name, ".") == 0 || strcmp (entry->d_name, "..") == 0) continue;

      if ((name = malloc (namesize)) == NULL)
	{
	  closedir (dir);
	  return (-1);
	}
      sprintf (name, "%s/%s", directory, entry->d_name);
//      printf ("Name of entry is %s\n", name);
      // Check the type of the file.

      stat (name, &statbuf);
      // If it's regular, add it to the plugin set.
      if (S_ISREG (statbuf.st_mode))
	{
	  void *plugin = plugin_load (name);
	  if (plugin != NULL)
	    {
//              printf ("plugin loaded!\n");
	      // Get the format_plugin.
	      format_plugin *pl = new_format_plugin (plugin);
	      if (pl == NULL) plugin_close (plugin);
	      // Add it to pset.
	      else plugin_set_add (pset, pl);
	    }
//          else
//            {
//              printf ("plugin failed!  %s\n", dlerror ());
//            }  
	}
      // If it's a directory, recurse.
      else if (S_ISDIR (statbuf.st_mode)) plugin_scandir (pset, name);
      free (name);
    }
  closedir (dir);
  return (0);
}

//=============================================================================
// Create a new format plugin, given the address of a plugin handle.
//
// Upon failure of any sort, NULL is returned.
//=============================================================================
static format_plugin *new_format_plugin (void *handle)
{
  format_plugin *retval = NULL;
  woofs_format_plugin *p = NULL;

  if ((p = plugin_getsym (handle, "woofs_struct")) == NULL) return (NULL);
  if ((retval = malloc (sizeof (format_plugin))) == NULL) return (NULL);
  retval->plugin_handle = handle;
  retval->plugin_struct = p;

  if (!plugin_verify (retval))
    {
      fprintf (stderr, "Plugin could not be verified.\n");
      free (retval);
      return (NULL);
    }
  if (retval->plugin_struct->init) retval->plugin_struct->init ();
  return (retval);
}

//=============================================================================
// Destroy the given format plugin.
//=============================================================================
static void free_format_plugin (format_plugin *p)
{
  if (p->plugin_struct->fini) p->plugin_struct->fini ();
  plugin_close (p->plugin_handle);
  free (p);
}

//=============================================================================
// Verify that the given plugin is, in fact, for us and that all required
// fields in the struct are filled in.
//=============================================================================
int plugin_verify (format_plugin *p)
{
  if (p->plugin_struct->magic != WOOFS_PLUGIN_MAGIC) printf ("magic\n");
  if (p->plugin_struct->claim_file == NULL) printf ("claim_file\n");
  if (p->plugin_struct->get_tags == NULL) printf ("get_tags\n");
  return (p->plugin_struct->magic == WOOFS_PLUGIN_MAGIC 
          && p->plugin_struct->claim_file != NULL
	  && p->plugin_struct->get_tags != NULL);
}

//=============================================================================
// Create an empty plugin set.
//=============================================================================
plugin_set *plugin_set_new (void)
{
  return (new_list ());
}

//=============================================================================
// Add a format_plugin to the given plugin set.
//=============================================================================
int plugin_set_add (plugin_set *pset, format_plugin *fp)
{
  return (list_insert (pset, pset->length, fp));
}

//=============================================================================
// Get an iterator for the given plugin set.
//=============================================================================
plugin_set_iter *plugin_set_get_iter (plugin_set *pset)
{
  return (pset->head);
}

//=============================================================================
//=============================================================================
void plugin_set_iter_advance (plugin_set_iter **iter)
{
  if (*iter != NULL) *iter = (*iter)->next;
}

//=============================================================================
//=============================================================================
int plugin_set_iter_hasmore (plugin_set_iter *iter)
{
  return !(iter == NULL);
}

//=============================================================================
// Clean up the given plugin set.
//=============================================================================
void plugin_set_fini (plugin_set *pset)
{
  list_node *iter = list_get_iter (pset);
  while (list_iter_hasmore (iter))
    {
      free_format_plugin (iter->data);
      list_iter_advance (iter);
    }
  free_list (pset);
}





//============ Very low-level functions for loading shared libraries.==========


//=============================================================================
// Load the given plugin into memory.
//=============================================================================
static void *plugin_load (char *filename)
{
  return (dlopen (filename, RTLD_NOW));
}

//=============================================================================
// Unload the given plugin.
//=============================================================================
static int plugin_close (void *plugin)
{
  return (dlclose (plugin));
}

//=============================================================================
// Fetch the object associated with the given symbol.
//=============================================================================
static void *plugin_getsym (void *plugin, char *sym)
{
  return (dlsym (plugin, sym));
}

#ifdef PLUGIN_TEST
int main (int argc, char **argv)
{
  plugin_set *pset = NULL;
  format_plugin *pl = NULL;
  int fd = 0;
  char *filename = NULL;
  if (argc != 3)
    {
      fprintf (stderr, "Usage: ./plugin <directory> <file>\n");
      return (-1);
    }

  filename = argv[2];
  fd = open (filename, O_RDONLY);
  if (fd < 0)
    {
      fprintf (stderr, "Can't open file!\n");
      return (-1);
    }
  pset = plugin_set_new ();
  if (plugin_scandir (pset, argv[1]) < 0)
    {
      fprintf (stderr, "Error loading plugins!\n");
      return (-1);
    }
  pl = plugin_claimfile (pset, fd, filename);
  if (pl == NULL)
    {
      fprintf (stderr, "No one claimed the file!\n");
    }
  else
    {
      pl->plugin_struct->get_tags (fd, filename, NULL);
    }
  plugin_set_fini (pset);
  return (0);
}
#endif
